import 'dart:async';
import 'dart:convert';

import 'package:get/get.dart';
import 'package:native_dart_package/global.dart';
import 'package:web_socket_channel/io.dart';
import 'package:yt_devops/components/log_utils.dart';
import 'package:yt_devops/configs.dart';
import 'package:yt_devops/services/predicates.dart';
import 'package:yt_devops/utils/func_utils.dart';

import 'socket_log.dart';

typedef ReceivedMessageCallback = void Function(dynamic);

enum ExitCode {
  close(3001);

  final int code;
  const ExitCode(this.code);
}

const Map<ExitCode, dynamic> Exit_Reason = {
  ExitCode.close: 'closed by user',
};

enum RequestCommandEnum {
  subscribe,
  unsubscribe,
}

enum ConnectStatus {
  close,
  connecting,
  success,
}

enum MessageEventType {
  // 设备实时数据
  deviceRealData(1),
  // 设备告警数据
  deviceEventData(2),
  // 心跳消息
  heartbeat(3),
  // 提示信息, 连接成功
  tips(4),
  // 设备日志数据
  deviceLog(5),
  //监测数据
  monitorData(6),
  // 网络状态
  networkStatus(7);

  final int value;
  const MessageEventType(this.value);
}

///心跳周期 30s
// ignore: constant_identifier_names
const int Heart_Beat_Period = 30;

class RequestMessageBody {
  RequestCommandEnum? command;
  String? device;
  List? keys;
  String? product;
  List? params;

  RequestMessageBody(
      {this.command, this.device, this.keys, this.product, this.params});

  Map<String, dynamic> toMap() => {
        'command': command?.name,
        'device': device,
        'keys': keys,
        'product': product,
        'params': params
      };
}

class RequestMessageType {
  MessageEventType? type;
  RequestMessageBody? data;
  RequestMessageType({this.type, this.data});
  Map<String, dynamic> toMap() => {'type': type?.value, 'data': data?.toMap()};
}

class UseSocket {
  static UseSocket? instance;

  static ConnectStatus connectedStatus = ConnectStatus.close;

  static int retryCount = 0;

  int MAX_RETRY = 3;

  IOWebSocketChannel? client;

  Future? _clientReseolve;

  StreamSubscription? _clientSubscription;

  final List<ReceivedMessageCallback> _receivedMessageCallbacks = [];

  String? url;

  void Function()? onConnectedSuccess;

  void Function()? onConnectedError;

  /// 心跳周期
  Timer? _heartBeatSubscription;

  UseSocket({this.url, this.onConnectedSuccess, this.onConnectedError});

  static UseSocket? getInstance(
      {String? url,
      void Function()? onConnectedSuccess,
      void Function()? onConnectedError}) {
    if (instance == null) {
      return instance = UseSocket(
          url: url,
          onConnectedSuccess: onConnectedSuccess,
          onConnectedError: onConnectedError);
    }
    return instance;
  }

  ///
  /// 获取token
  ///
  String? _getAccessToken() {
    return Global.userAuth?.token;
  }

  ///
  /// 替换socket请求头
  ///
  String _getWebSocketHost() {
    var api = Configs.enviromentModel.api;
    Log.d('enviromentModel===$api');
    if (api.startsWith('https')) {
      api = api.replaceFirst(RegExp('https'), 'wss');
    } else if (api.startsWith('http')) {
      api = api.replaceFirst(RegExp('http'), 'wss');
    }
    return api;
  }

  bool isConnecting() {
    return connectedStatus != ConnectStatus.close;
  }

  reconnect() {
    if (isConnecting()) {
      return;
    }
    _tryConnectedUntilSuccess();
  }

  close() {
    Log.d('===== 关闭socket =====');
    connectedStatus = ConnectStatus.close;
    _heartBeatSubscription?.cancel();
    _clientSubscription?.cancel();
    client?.sink.close(ExitCode.close.code, Exit_Reason[ExitCode.close]);
  }

  reset() {
    _receivedMessageCallbacks.length = 0;
    retryCount = 0;
    connectedStatus = ConnectStatus.close;
  }

  ///
  /// 连接服务端
  ///
  _tryConnectedUntilSuccess() {
    try {
      printInfo(info: 'websocket:before connected ');
      var token = _getAccessToken();
      if (FuncUtils.isEmpty(token)) {
        throw Exception('Connection: token is invalid');
      }
      connectedStatus = ConnectStatus.connecting;

      var path = url != null
          ? '$url/$token'
          : '${_getWebSocketHost()}$websocket/$token';

      client = IOWebSocketChannel.connect(path);

      _clientReseolve = client?.ready.then((value) {
        connectedStatus = ConnectStatus.success;
        startHeartBeat();
        onConnectedSuccess?.call();
      });

      _clientSubscription = client?.stream.listen((event) {
        if (_receivedMessageCallbacks.isEmpty) {
          return;
        }
        var data = jsonDecode(event);
        SocketLogger(response: data).printLog();

        if (data['type'] == MessageEventType.heartbeat.value ||
            data['type'] == MessageEventType.tips.value) {
          return;
        }
        for (var cb in _receivedMessageCallbacks) {
          cb(data);
        }
      }, onDone: () {
        connectedStatus = ConnectStatus.close;
        if (client?.closeCode == ExitCode.close.code) {
          reset();
        } else if (retryCount < MAX_RETRY) {
          retryCount++;
          _tryConnectedUntilSuccess();
        }
      }, onError: (err) {
        onConnectedError?.call();
        printError(info: 'websocket: connected error $err');
      });
    } catch (error) {
      printError(info: 'websocket: catch error $error');
    }
  }

  ///
  /// 发送心跳
  ///
  startHeartBeat() {
    _heartBeatSubscription?.cancel();
    _heartBeatSubscription =
        Timer.periodic(const Duration(seconds: Heart_Beat_Period), (timer) {
      _clientReseolve?.then((value) {
        var json = jsonEncode(
            {'data': 'ping', 'type': MessageEventType.heartbeat.value});
        client?.sink.add(json);
        Log.d('发送心跳💗💗💗');
      });
    });
  }

  ///
  /// 给服务端发送数据
  ///
  sendMessage(RequestMessageType data) {
    _clientReseolve?.then((value) {
      var json = jsonEncode(data.toMap());
      print('socket send message $json');

      client?.sink.add(jsonEncode(data.toMap()));
    });
  }

  ///
  /// 添加回调
  ///
  addReceivedMessageCallback(ReceivedMessageCallback callback) {
    _receivedMessageCallbacks.add(callback);
  }

  removeReceivedMessageCallback(ReceivedMessageCallback callback) {
    _receivedMessageCallbacks.removeWhere((fn) => fn == callback);
  }
}
